/*   This file is a part of the WBTS Project. WBTS is a system for helping 
 *	 researchers, and browser implementers to find security related bugs.
 *   Copyright (C) 2010 Isaac Dawson
 *
 *    WBTS is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

// header ref: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html //
function Headers() {
	this.safe_header = 'If-Modified-Since'; // safe because it works even in IE Mobile.
	this.safe_header2 = 'If-None-Match';
	this.banned_header = 'Host';
	this.banned_value = 'victim.com';
	this.current_header = 0;
	this.headers = [
		"Accept", "text/wbts", 
		"Accept-Charset", "ISO-WBTS",
		"Accept-Encoding", "wbts,gzip,deflate",
		"Accept-Language", "wb-ts",
		"Cache-Control", "max-age=80085",
		"Cookie", "wbts=1",
		"If-Modified-Since", "Wbts",
		"If-Unmodified-Since","Wbts",
		"Max-Forwards","1",
		"Proxy-Authorization","Basic d2J0czo=", // wbts:
		"Referer", "http://www.victim.com/",
		"Origin", "http://www.victim.com",
		"TE","wbts,gzip,deflate",
		"User-Agent","WBTS/1.0",
		"Vary","*",
		"If-Match","wbts",
		"If-None-Match","wbts",
		"If-Range", "80085",
		"Authorization","Basic d2J0czo=", 
		"Connection","wbts",
		"Sec-WebSocket-Key1", "12a 3 4a45 8",
		"Sec-WebSocket-Key2", "1a2 3 4a45 8",
		"Sec-WebSocket-Protocol", "wbts",
		"Upgrade", "websocket",
		"Content-Encoding","wbts,gzip,deflate",
		"Content-Length","80085",
		"Expect","100-continue",
		"From","hack@me.org",
		"Host","www.victim.com",
		"X-Arbitrary-Header","wbts",
		"SomeHeader","wbts"
	];
	// change these if you want the test to fail on specific headers.
	// but you must keep them all lower case!
	this.banned_headers = ["host",
		"sec-websocket-key1",
		"sec-websocket-key2",
		"sec-websocket-protocol",
		"upgrade",
		"origin",
		"accept-charset",
		"accept-encoding",
		"accept-language",
		"cookie",
		"referer",
		"transfer-encoding",
		"user-agent",
		"via"
	];
	this.name = this.headers[this.current_header];
	this.value = this.headers[this.current_header+1];
	this.total = this.headers.length;
}
Headers.prototype.listHeaders = function() {
	var headers = '';
	for (var i = 0; i < this.total; i+=2) {
		headers += this.headers[i] + ": " + this.headers[i+1] + '\r\n';
	}
	return headers;
}
Headers.prototype.next = function() {
	this.current_header += 2;
	if (this.current_header == this.total)
		return -1;
	this.name = this.headers[this.current_header];
	this.value = this.headers[this.current_header+1];
	return this.current_header;
}

Headers.prototype.add = function(name, value) {
	this.headers.push(name);
	this.headers.push(value);
	this.total += 2;
}

Headers.prototype.addBanned = function(name, value) {
	this.banned_headers.push(name);
	this.banned_headers.push(value);
}

/*
	checkBanned has three possibilities:
	1. Doesn't exist at all returns DISALLOWED
	2. Header found, check if it is an exact match of header/value then check if it is banned
	if banned, set ALLOWED BANNED and return, if not, just set ALLOWED.
	3. Header found, check if our value is *somewhere* in that request line if it's not found,
	just set to disallowed and return
	4. Header found and our value was found *somewhere* in that request line, check if that header
	is banned, if so return ALLOWED BANNED. If it's not banned just return ALLOWED.
	@params
	response_data - the responseText of a showRequest, request.
	tc - the testcase
	@returns
	0 - disallowed
	1 - allowed
	2 - allowed a banned header
*/
Headers.prototype.checkBanned = function(response_data, tc) {
	var header_data = this.name + ": " + this.value;
	var result_lc = response_data.toLowerCase();
	var hdr_index = result_lc.indexOf(this.name.toLowerCase()+": ");
	// make sure we even have the header
	if (hdr_index == -1) {
		tc.output += header_data + ' DISALLOWED\r\n';
		return 0;
	} else {
	// we do have our header
		var split_header = response_data.substring(hdr_index, response_data.length);
		var result_line = split_header.substring(0, split_header.indexOf('\r'));
		var result_line_lc = result_line.toLowerCase();
		// exact match
		if (result_line_lc == header_data.toLowerCase()) {
			// check if banned
			if (this.isBanned(tc, this.name, result_line) == 2) {
				return 2;
			}
			// not banned but allowed, no need to search for value
			tc.output += '<b>' + result_line + ' ALLOWED</b>\r\n';
			return 1;
		// not an exact match search for our value
		} else if (result_line_lc.indexOf(this.value.toLowerCase()) == -1) {
			// value not found set to DISALLOWED
			tc.output += header_data + ' DISALLOWED\r\n';
			return 0;
		} else {
			// value was found (somewhere) see if it is a banned header
			if (this.isBanned(tc, this.name, result_line) == 2) {
				return 2;
			}
			// it's not a banned header, but the browser allows it.
			tc.output += '<b>' + result_line + ' ALLOWED</b>\r\n';
			return 1;
		}
	}
	return 0;
}
/*
Similiar to checkBanned but we just want to see if it even exists.
*/
Headers.prototype.checkExists = function(response_data, tc) {
	var header_data = this.banned_header + ": " + this.banned_value;
	var result_lc = response_data.toLowerCase();
	var hdr_index = result_lc.indexOf(this.banned_header.toLowerCase()+": ");
	// make sure we even have the header
	if (hdr_index == -1) {
		tc.output += header_data + ' NOT FOUND\r\n';
		return 0;
	} else {
	// we do have our header
		var split_header = response_data.substring(hdr_index, response_data.length);
		var result_line = split_header.substring(0, split_header.indexOf('\r'));
		var result_line_lc = result_line.toLowerCase();
		// exact match
		if (result_line_lc == header_data.toLowerCase()) {
			tc.output += '<b><font color="red">' + result_line + ' FOUND</font></b>\r\n';
			return 1;
		// not an exact match search for our value
		} else if (result_line_lc.indexOf(this.value.toLowerCase()) == -1) {
			// value not found set to NOT FOUND
			tc.output += header_data + ' NOT FOUND\r\n';
			return 0;
		} else {
			// Value exists somewhere, set FOUND.
			tc.output += '<b><font color="red">' + result_line + ' FOUND</font></b>\r\n';
			return 1;
		}
	}
	return 0;
}

// check if the header we supplied is listed in our banned headers
// if so, set the test to failing and print out the result line as is.
Headers.prototype.isBanned = function(tc, header_name, result_line) {
	for (var i = 0; i < this.banned_headers.length; ++i) {
		if (this.banned_headers[i] == header_name.toLowerCase()) {
			tc.output += '<b><font color="red">' + result_line + ' ALLOWED BANNED</font></b>\r\n';
			tc.test_passed = 'false';
			return 2;
		}
	}
}